package cn.dengta.webapp.user.service.task;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Resource;

import cn.dengta.common.model.Status;
import cn.dengta.common.model.TimeInterval;
import cn.dengta.common.web.Page;
import cn.dengta.context.model.RedisKey;
import cn.dengta.context.model.WesKit;
import cn.dengta.context.rpc.RpcContextUtil;
import cn.dengta.context.web.RequestContextImpl;
import cn.dengta.webapp.base.service.BaseServiceImpl;
import cn.dengta.webapp.user.dao.TaskRuleDao;
import cn.dengta.webapp.user.entity.task.TaskRule;
import cn.dengta.webapp.user.entity.task.TaskRule.TaskRuleType;
import cn.dengta.webapp.user.model.Gender;
import cn.dengta.webapp.user.model.TaskType;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import me.codeplayer.util.*;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

/**
 * 任务规则表 服务实现类
 *
 * @author lgy
 * @since 2022-03-05
 */
@Service
public class TaskRuleService extends BaseServiceImpl<TaskRule, TaskRuleDao, Long> {

	@Resource
	RedisTemplate<String, Object> redisTemplate;

	/**
	 * 根据查询条件返回任务规则列表
	 */
	public Page<TaskRule> findPage(Page<TaskRule> page, TaskRule input, TimeInterval validTimeInterval) {
		return baseDao.selectPage(page, popWrapper(X.map(input, TaskRule::getName), X.map(input, TaskRule::getType), X.map(input, TaskRule::getRuleType), validTimeInterval));
	}

	public List<TaskRule> find(TaskRule input, TimeInterval validTimeInterval) {
		return baseDao.selectList(popWrapper(X.map(input, TaskRule::getName), X.map(input, TaskRule::getType), X.map(input, TaskRule::getRuleType), validTimeInterval));
	}

	public List<TaskRule> findValid(TaskType type) {
		Date now = RequestContextImpl.get().now();
		QueryWrapper<TaskRule> wrapper = new QueryWrapper<TaskRule>()
				.ge(TaskRule.STATUS, Status.YES.value)
				.eq(type != null, TaskRule.TYPE, type)
				.gt(TaskRule.END_TIME, now);
		return baseDao.selectList(wrapper);
	}

	public QueryWrapper<TaskRule> popWrapper(String name, TaskType taskType, TaskRuleType ruleType, TimeInterval validTimeInterval) {
		QueryWrapper<TaskRule> wrapper = new QueryWrapper<>();
		wrapper.like(StringUtil.notEmpty(name), TaskRule.NAME, name)
				.eq(taskType != null, TaskRule.TYPE, taskType)
				.eq(ruleType != null, TaskRule.RULE_TYPE, ruleType);
		if (validTimeInterval != null) {
			wrapper.ge(validTimeInterval.getBegin() != null, TaskRule.BEGIN_TIME, validTimeInterval.getFinalBegin());
			wrapper.le(validTimeInterval.getEnd() != null, TaskRule.END_TIME, validTimeInterval.getFinalEnd());
		}
		return wrapper;
	}

	/**
	 * 返回指定任务类型的规则
	 */
	public List<TaskRule> getByRuleType(TaskRuleType ruleType) {
		return getByRuleType(null, null, new TaskRule.TaskRuleType[] { ruleType });
	}

	public List<TaskRule> getByRuleType(WesKit wesKit, Gender gender, TaskRuleType[] ruleType) {
		QueryWrapper<TaskRule> wrapper = new QueryWrapper<TaskRule>()
				.in(TaskRule.RULE_TYPE, (Object[]) ruleType)
				.gt(TaskRule.END_TIME, RequestContextImpl.get().now())
				.ge(TaskRule.STATUS, Status.YES.value);
		if (wesKit != null) {
			wrapper.apply("FIND_IN_SET({0}, wes_kits)", wesKit.getValue());
		}
		if (gender != null) {
			wrapper.and(w -> w.apply("gender = {0} OR gender IS NULL", gender.getValue()));
		}
		return baseDao.selectList(wrapper);
	}

	/**
	 * 获取单个任务规则
	 */
	public TaskRule getByRuleTypeOne(TaskRuleType ruleType) {
		List<TaskRule> rules = getByRuleType(ruleType);
		return X.isValid(rules) ? rules.get(0) : null;
	}

	/**
	 * 缓存任务规则（一天）
	 */
	protected Map<String, Object> taskRuleLoader(boolean flush, TaskType type) {
		HashOperations<String, String, Object> opsForHash = redisTemplate.opsForHash();
		final String key = RedisKey.TASK_RULE_LOADER;
		Map<String, Object> loader = null;
		if (type != null) {
			final String typeKey = type.name().toLowerCase();
			List<List<TaskRule>> rules = X.castType(opsForHash.multiGet(key, List.of(typeKey)));
			if (X.isValid(rules) && X.isValid(rules.get(0))) {
				List<TaskRule> ruleList = rules.get(0).stream().filter(Objects::nonNull).collect(Collectors.toList());
				if (X.isValid(ruleList)) {
					loader = CollectionUtil.ofHashMap(typeKey, ruleList);
				}
			}
		} else {
			loader = opsForHash.entries(key);
		}
		if (flush || !X.isValid(loader)) {
			if (type != null) {
				List<TaskRule> ruleList = findValid(type);
				if (X.isValid(ruleList)) {
					opsForHash.put(key, type.name().toLowerCase(), ruleList);
					redisTemplate.expire(key, EasyDate.MILLIS_OF_DAY, TimeUnit.MILLISECONDS);
				}
			} else {
				List<TaskRule> ruleList = findValid(null);
				if (X.isValid(ruleList)) {
					opsForHash.putAll(key, loader = X.castType(ruleList.stream().collect(Collectors.groupingBy(t -> t.getType().name().toLowerCase(), LinkedHashMap::new, Collectors.toList()))));
					redisTemplate.expire(key, EasyDate.MILLIS_OF_DAY, TimeUnit.MILLISECONDS);
				}
			}
		}
		return loader;
	}

	public Map<String, List<TaskRule>> getByTypes(WesKit wesKit, Gender gender, boolean flush, List<TaskRuleType> excludeTaskRuleTypes, @Nonnull TaskType... types) {
		final Map<String, List<TaskRule>> ruleMap = X.castType(taskRuleLoader(flush, null));
		return X.isValid(ruleMap) ? getTypeMapOfCache(wesKit, gender, ruleMap, false, excludeTaskRuleTypes, types) : Collections.emptyMap();
	}

	public List<TaskRule> getByTypesList(WesKit wesKit, Gender gender, boolean flush, @Nonnull TaskType... types) {
		return getByTypes(wesKit, gender, flush, null, types).values().stream().reduce(new ArrayList<>(), (k1, k2) -> {
			k1.addAll(k2);
			return k1;
		});
	}

	public List<TaskRule> getByTypesList(WesKit wesKit, Gender gender, boolean flush, TaskRuleType taskRuleType, @Nonnull TaskType... types) {
		return getByTypesList(wesKit, gender, flush, types).stream().filter(t -> t.getRuleType() == taskRuleType).collect(Collectors.toList());
	}

	public int calcMaxLimit(List<TaskRule> taskRules) {
		int maxLimit = 0;
		for (TaskRule taskRule : taskRules) {
			if (taskRule.getMaxLimit() != null && taskRule.getMaxLimit() > maxLimit) {
				maxLimit = taskRule.getMaxLimit();
			}
		}
		return maxLimit;
	}

	public int calcMaxLimit(WesKit wesKit, Gender gender, TaskRuleType taskRuleType) {
		List<TaskRule> taskRules = getByTypesList(wesKit, gender, false, taskRuleType, TaskType.DAILY, TaskType.FOREVER);
		return calcMaxLimit(taskRules);
	}

	@Nonnull
	private Map<String, List<TaskRule>> getTypeMapOfCache(WesKit wesKit, Gender gender, Map<String, List<TaskRule>> ruleMap, boolean allowVisible, List<TaskRuleType> excludeTaskRuleTypes, @Nonnull TaskType... types) {
		final Map<String, List<TaskRule>> map = new HashMap<>(ruleMap.size());
		String name;
		final RequestContextImpl req = RequestContextImpl.get();
		final long nowTime = req.now().getTime();
		for (TaskType type : types) {
			name = type.name().toLowerCase();
			List<TaskRule> trs = ruleMap.get(name);
			if (trs == null) {
				Map<String, Object> typeMap = taskRuleLoader(true, type);
				if (X.isValid(typeMap)) {
					trs = X.castType(typeMap.get(name));
				}
			}
			if (X.isValid(trs)) {
				trs = trs.stream().filter(t -> t.isActivity(nowTime)
						&& (allowVisible || t.getRuleType().visible)
						&& (t.getRuleType().gender == null || gender == null || gender == t.getRuleType().gender)
						&& (t.getGender() == null || gender == t.getGender())
						&& (WesKit.isWesKit(wesKit, t.getWesKits()))
						&& (excludeTaskRuleTypes == null || !(excludeTaskRuleTypes.contains(t.getRuleType())))
				).collect(Collectors.toList());
			}
			map.put(name, X.isValid(trs) ? trs : Collections.emptyList());
		}
		return map;
	}

	public TaskRule getCacheByRuleType(WesKit wesKit, Gender gender, @Nonnull TaskType type, boolean allowVisible, @Nonnull TaskRuleType ruleType) {
		Map<String, List<TaskRule>> ruleMap = X.castType(taskRuleLoader(false, type));
		List<TaskRule> ruleList;
		if (!X.isValid(ruleMap) || !X.isValid((ruleMap = getTypeMapOfCache(wesKit, gender, ruleMap, allowVisible, null, type))) || !X.isValid(ruleList = ruleMap.get(type.name().toLowerCase()))) {
			return null;
		}
		return CollectionUtil.findFirst(ruleList, t -> t.getRuleType() == ruleType);
	}

}
